"""Event-driven socket connection."""

import asyncio

from eventkit import Event


class Connection(asyncio.Protocol):
    """
    Event-driven socket connection.

    Events:
        * ``hasData`` (data: bytes):
          Emits the received socket data.
        * ``disconnected`` (msg: str):
          Is emitted on socket disconnect, with an error message in case
          of error, or an empty string in case of a normal disconnect.
    """

    def __init__(self):
        self.hasData = Event("hasData")
        self.disconnected = Event("disconnected")
        self.reset()

    def reset(self):
        self.transport = None
        self.numBytesSent = 0
        self.numMsgSent = 0

    async def connectAsync(self, host, port):
        if self.transport:
            # wait until a previous connection is finished closing
            self.disconnect()
            await self.disconnected
        self.reset()
        # Use get_running_loop() directly for optimal performance in async context
        loop = asyncio.get_running_loop()
        self.transport, _ = await loop.create_connection(lambda: self, host, port)

    def disconnect(self):
        if self.transport:
            self.transport.write_eof()
            try:
                # sometimes the loop is already closed, but we don't
                # want the user to get a runtime exception from deep in
                # this library, so we just hide any failure to double close
                # the event loop.
                self.transport.close()
            except RuntimeError:
                # prevents: raise RuntimeError('Event loop is closed')
                pass

    def isConnected(self):
        return self.transport is not None

    def sendMsg(self, msg):
        if self.transport:
            self.transport.write(msg)
            self.numBytesSent += len(msg)
            self.numMsgSent += 1

    def connection_lost(self, exc):
        self.transport = None
        msg = str(exc) if exc else ""
        self.disconnected.emit(msg)

    def data_received(self, data):
        self.hasData.emit(data)
